package busterminal.command.scheduleditem;

import busterminal.command.AbstractCommand;
import busterminal.entity.Route;
import busterminal.entity.ScheduledItem;
import busterminal.entity.ScheduledItemImplementation;
import busterminal.entity.service.DaysOfWeek;
import busterminal.entity.service.Time;
import busterminal.exception.BusinessException;
import busterminal.persistence.RouteDAO;
import busterminal.persistence.ScheduledItemDAO;
import busterminal.services.ServiceLocator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Команда для добавления нового пункта расписания
 */
public class CreateScheduledItemCommand extends AbstractCommand {

    /**
     * Имя параметра "Маршрут, по которому осуществляется рейс"
     */
    private static final String PARAM_ROUTE_ID = "route id";
    /**
     * Имя параметра "Время отправления"
     */
    private static final String PARAM_DEPARTURE_TIME = "departure time (in HH:MM format)";
    /**
     * Имя параметра "Время прибытия"
     */
    private static final String PARAM_ARRIVAL_TIME = "arrival time (in HH:MM format)";
    /**
     * Имя параметра "Время отправления с конечного пункта"
     */
    private static final String PARAM_DEPARTURE_TIME_FROM_END_POINT = "departure time from end point (in HH:MM format)";
    /**
     * Имя параметра "Время прибытия на автовокзал"
     */
    private static final String PARAM_ARRIVAL_TIME_TO_BUS_TERMINAL = "arrival time to bus terminal (in HH:MM format)";
    /**
     * Имя параметра "Дни недели осуществления рейсов по маршруту"
     */
    private static final String PARAM_ROUTING_DAYS = "routing days (separated by comma)";
    /**
     * Имя параметра "Номер платформы, с которой будет отправляться автобус по определённому маршруту"
     */
    private static final String PARAM_PLATFORM_NUMBER = "platform number";
    /**
     * Разделитель для параметра "Дни недели осуществления рейсов по маршруту"
     */
    private static final String SEPARATOR = "\\s*,\\s*";
    /**
     * Регулярное выражение для проверки корректности ввода списка c элементами, разделёнными запятой
     */
    private static final String CORRECT_LIST_FORMAT = "^((\\s*)([A-Za-z]+)(\\s*),)*((\\s*)([A-Za-z]+)(\\s*))$";
    /**
     * Регулярное выражение для проверки корректности ввода числа
     */
    private static final String CORRECT_NUMBER_FORMAT = "^[1-9]([0-9]*)$";
    /**
     * Регулярное выражение для проверки корректности ввода времени в формате <code>HH:MM</code>
     */
    private static final String CORRECT_TIME_FORMAT = "^(([0-1][0-9])|([2][0-3])):([0-5][0-9])$";

    /**
     * Конструктор по умолчанию
     */
    public CreateScheduledItemCommand() {
        super("Add new scheduled item");
    }

    /**
     * Получить список имён параметров
     * @return список имён параметров
     */
    @Override
    public List<String> getParametersNames() {
        return Arrays.asList(PARAM_ROUTE_ID, PARAM_DEPARTURE_TIME, PARAM_ARRIVAL_TIME, PARAM_DEPARTURE_TIME_FROM_END_POINT, PARAM_ARRIVAL_TIME_TO_BUS_TERMINAL, PARAM_ROUTING_DAYS, PARAM_PLATFORM_NUMBER);
    }

    /**
     * Выполнить команду
     */
    @Override
    public void execute() {
        ScheduledItem scheduledItem = new ScheduledItemImplementation();

        String paramRouteId = parameters.get(PARAM_ROUTE_ID).trim();
        if (!paramRouteId.matches(CORRECT_NUMBER_FORMAT)) {
            throw new BusinessException("Incorrect route id='" + paramRouteId + "'");
        }
        int routeId;
        try {
            routeId = Integer.parseInt(paramRouteId);
        } catch (Exception e) {
            throw new BusinessException("Incorrect route id='" + paramRouteId + "'; " + e.toString(), e);
        }
        Route route = ServiceLocator.getService(RouteDAO.class).read(routeId);
        if (route == null) {
            throw new BusinessException("Cannot find route with id='" + routeId + "'");
        }
        scheduledItem.setRoute(route);

        if (!parameters.get(PARAM_DEPARTURE_TIME).trim().matches(CORRECT_TIME_FORMAT)) {
            throw new BusinessException("Incorrect time '" + parameters.get(PARAM_DEPARTURE_TIME).trim() + "'");
        }
        try {
            scheduledItem.setDepartureTime(Time.parseTime(parameters.get(PARAM_DEPARTURE_TIME).trim()));
        } catch (Exception e) {
            throw new BusinessException("Incorrect time '" + parameters.get(PARAM_DEPARTURE_TIME) + "'; " + e.toString(), e);
        }

        if (!parameters.get(PARAM_ARRIVAL_TIME).trim().matches(CORRECT_TIME_FORMAT)) {
            throw new BusinessException("Incorrect time '" + parameters.get(PARAM_ARRIVAL_TIME).trim() + "'");
        }
        try {
            scheduledItem.setArrivalTime(Time.parseTime(parameters.get(PARAM_ARRIVAL_TIME).trim()));
        } catch (Exception e) {
            throw new BusinessException("Incorrect time '" + parameters.get(PARAM_ARRIVAL_TIME).trim() + "'; " + e.toString(), e);
        }

        scheduledItem.setTravelTime();

        if (!parameters.get(PARAM_DEPARTURE_TIME_FROM_END_POINT).trim().matches(CORRECT_TIME_FORMAT)) {
            throw new BusinessException("Incorrect time '" + parameters.get(PARAM_DEPARTURE_TIME_FROM_END_POINT).trim() + "'");
        }
        try {
            scheduledItem.setDepartureTimeFromEndPoint(Time.parseTime(parameters.get(PARAM_DEPARTURE_TIME_FROM_END_POINT).trim()));
        } catch (Exception e) {
            throw new BusinessException("Incorrect time '" + parameters.get(PARAM_DEPARTURE_TIME_FROM_END_POINT).trim() + "'; " + e.toString(), e);
        }

        if (!parameters.get(PARAM_ARRIVAL_TIME_TO_BUS_TERMINAL).trim().matches(CORRECT_TIME_FORMAT)) {
            throw new BusinessException("Incorrect time '" + parameters.get(PARAM_ARRIVAL_TIME_TO_BUS_TERMINAL).trim() + "'");
        }
        try {
            scheduledItem.setArrivalTimeToBusTerminal(Time.parseTime(parameters.get(PARAM_ARRIVAL_TIME_TO_BUS_TERMINAL).trim()));
        } catch (Exception e) {
            throw new BusinessException("Incorrect time '" + parameters.get(PARAM_ARRIVAL_TIME_TO_BUS_TERMINAL).trim() + "'; " + e.toString(), e);
        }

        if (!parameters.get(PARAM_ROUTING_DAYS).matches(CORRECT_LIST_FORMAT)) {
            throw new BusinessException("Incorrect days list: '" + parameters.get(PARAM_ROUTING_DAYS).trim() + "'");
        }
        List<String> inputData = Arrays.asList(parameters.get(PARAM_ROUTING_DAYS).trim().toUpperCase().split(SEPARATOR));
        List<DaysOfWeek> foundDays = new ArrayList<DaysOfWeek>();
        for (String day : inputData) {
            try {
                foundDays.add(DaysOfWeek.valueOf(day));
            } catch (Exception e) {
                throw new BusinessException("Incorrect day name: '" + day + "'; " + e.toString(), e);
            }
        }
        scheduledItem.setRoutingDays(foundDays);

        if (!parameters.get(PARAM_PLATFORM_NUMBER).trim().matches(CORRECT_NUMBER_FORMAT)) {
            throw new BusinessException("Incorrect platform number: '" + parameters.get(PARAM_PLATFORM_NUMBER).trim() + "'");
        }
        try {
            scheduledItem.setPlatformNumber(Integer.parseInt(parameters.get(PARAM_PLATFORM_NUMBER).trim()));
        } catch (Exception e) {
            throw new BusinessException("Incorrect platform number: '" + parameters.get(PARAM_PLATFORM_NUMBER).trim() + "'; " + e.toString(), e);
        }

        ServiceLocator.getService(ScheduledItemDAO.class).create(scheduledItem);
    }

    /**
     * Вывести список доступных вариантов параметра с фиксированным набором значений
     * @param parameterName имя параметра с фиксированным набором значений
     */
    @Override
    public void showAccessableVariants(String parameterName) {
        if (PARAM_ROUTING_DAYS.equals(parameterName)) {
            System.out.println("Accessable variants: " + DaysOfWeek.getValues());
        }
    }
}
